/*
 * Copyright (C) 2001-2011 Jacek Sieka, arnetheduck on gmail point com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifndef DCPLUSPLUS_DCPP_UPLOAD_MANAGER_H
#define DCPLUSPLUS_DCPP_UPLOAD_MANAGER_H

#include "forward.h"
#include "UserConnectionListener.h"
#include "Singleton.h"
#include "UploadManagerListener.h"
#include "Client.h"
#include "ClientManager.h"
#include "ClientManagerListener.h"
#include "MerkleTree.h"
#include "FastAlloc.h"

struct WaitingUser
{
	HintedUser user;
	string token;
	
	WaitingUser(const HintedUser& _user, const std::string& _token) : user(_user), token(_token) { }
	
	operator const UserPtr&() const
	{
		return user.user;
	}
};

class UploadQueueItem : public FastAlloc<UploadQueueItem>, public intrusive_ptr_base<UploadQueueItem>, public UserInfoBase,
	public ColumnBase< 12 >  //[+]PPA
{
	public:
		//[!]IRainman default constructor replaced by a SMT, see cpp file ;)
		UploadQueueItem(const HintedUser& u, const string& _file, int64_t p, int64_t sz, uint64_t itime);// !SMT!-UI
		
		typedef vector<UploadQueueItem*> List;
		typedef deque<pair<WaitingUser, UploadQueueItem::List>> SlotQueue;
		typedef List::const_iterator Iter;
		
		void update();
		
		static int compareItems(const UploadQueueItem* a, const UploadQueueItem* b, uint8_t col)
		{
			//+BugMaster: small optimization; fix; correct IP sorting
			switch (col)
			{
				case COLUMN_FILE:
				case COLUMN_PATH:
				case COLUMN_NICK:
				case COLUMN_HUB:
					return stricmp(a->getText(col), b->getText(col));
				case COLUMN_TRANSFERRED:
					return compare(a->pos, b->pos);
				case COLUMN_SIZE:
					return compare(a->size, b->size);
				case COLUMN_ADDED:
				case COLUMN_WAITING:
					return compare(a->time, b->time);
				case COLUMN_SLOTS:
					return compare(a->slots, b->slots); // !SMT!-UI
				case COLUMN_SHARE:
					return compare(a->share, b->share); // !SMT!-UI
				case COLUMN_IP:
				{
					uint32_t a_ip = 0, b_ip = 0;
					unsigned x1, x2, x3, x4;
					if (sscanf(Text::fromT(a->getText(col)).c_str(), "%d.%d.%d.%d", &x1, &x2, &x3, &x4) == 4) //-V112
						a_ip = (x1 << 24) + (x2 << 16) + (x3 << 8) + x4;
					if (sscanf(Text::fromT(b->getText(col)).c_str(), "%d.%d.%d.%d", &x1, &x2, &x3, &x4) == 4) //-V112
						b_ip = (x1 << 24) + (x2 << 16) + (x3 << 8) + x4;
					return compare(a_ip, b_ip);
				}
			}
			return stricmp(a->getText(col), b->getText(col));
			//-BugMaster: small optimization; fix; correct IP sorting
			return 0;
		}
		
		enum
		{
			COLUMN_FIRST,
			COLUMN_FILE = COLUMN_FIRST,
			COLUMN_PATH,
			COLUMN_NICK,
			COLUMN_HUB,
			COLUMN_TRANSFERRED,
			COLUMN_SIZE,
			COLUMN_ADDED,
			COLUMN_WAITING,
			COLUMN_LOCATION, // !SMT!-IP
			COLUMN_IP, // !SMT!-IP
#ifdef PPA_INCLUDE_DNS
			COLUMN_DNS, // !SMT!-IP
#endif
			COLUMN_SLOTS, // !SMT!-UI
			COLUMN_SHARE, // !SMT!-UI
			COLUMN_LAST
		};
		
		const tstring getText(uint8_t col) const;
		int getImageIndex() const;
		
		const string& getFile() const
		{
			return file;
		}
		const UserPtr& getUser() const
		{
			return user.user;
		}
		const HintedUser& getHintedUser() const
		{
			return user;
		}
		int64_t getSize() const
		{
			return size;
		}
		uint64_t getTime() const
		{
			return time;
		}
		
		GETSET(int64_t, pos, Pos);
		GETSET(int, flagImage, FlagImage);  // !SMT!-IP
		
	private:
		string file;
		int64_t size;
		uint64_t time;
		uint64_t share; // !SMT!-UI
		int slots; // !SMT!-UI
		string ip;
#ifdef PPA_INCLUDE_DNS
		string dns;
#endif
		string country; // !SMT!-IP
		
		HintedUser user;
};

class UploadManager : private ClientManagerListener, private UserConnectionListener, public Speaker<UploadManagerListener>, private TimerManagerListener, public Singleton<UploadManager>
{
#ifdef PPA_INCLUDE_DOS_GUARD
		typedef unordered_map<string, int> CViewDoSCandidatMap;
		CViewDoSCandidatMap m_dos_map;
#endif
	public:
		/** @return Number of uploads. */
		size_t getUploadCount()
		{
			Lock l(cs);
			return uploads.size();
		}
		
		/**
		 * @remarks This is only used in the tray icons. Could be used in
		 * MainFrame too.
		 *
		 * @return Running average download speed in Bytes/s
		 */			
		int64_t getRunningAverage()
		{
			return runningAverage;//[+] IRainman refactoring transfer mechanism
		}
		
		int getSlots() const
		{
			return (max(SETTING(SLOTS), max(SETTING(HUB_SLOTS), 0) * Client::getTotalCounts()));
		}
		
		/** @return Number of free slots. */
		int getFreeSlots() const
		{
			return max((getSlots() - running), 0);
		}
		
		/** @internal */
		int getFreeExtraSlots() const
		{
			return max(SETTING(EXTRA_SLOTS) - getExtra(), 0);
		}
		
		/** @param aUser Reserve an upload slot for this user and connect. */
		void reserveSlot(const HintedUser& aUser, uint64_t aTime);
		void unreserveSlot(const UserPtr& aUser);
		void clearUserFiles(const UserPtr&);
		const UploadQueueItem::SlotQueue getUploadQueue();
		bool hasReservedSlot(const UserPtr& aUser)
		{
			Lock l(cs);
			return reservedSlots.find(aUser) != reservedSlots.end();
		}
		bool isConnecting(const UserPtr& aUser) const
		{
			return connectingUsers.find(aUser) != connectingUsers.end();
		}
		
		bool getFireballStatus() const
		{
			return isFireball;
		}
		bool getFileServerStatus() const
		{
			return isFileServer;
		}
		
		/** @internal */
		void addConnection(UserConnectionPtr conn);
		void removeDelayUpload(const UserPtr& aUser);
		void abortUpload(const string& aFile, bool waiting = true);
		
		GETSET(int, extraPartial, ExtraPartial);
		GETSET(int, extra, Extra);
		GETSET(uint64_t, lastGrant, LastGrant);
		
		void load(); // !SMT!-S
		void save(); // !SMT!-S
#ifdef IRAINMAN_ENABLE_AUTO_BAN
		bool isBanReply(const UserPtr& user); // !SMT!-S
#endif IRAINMAN_ENABLE_AUTO_BAN
		
		time_t getReservedSlotTime(const UserPtr& aUser) const;
	private:
		bool isFireball;
		bool isFileServer;
		int running;

		int64_t runningAverage;//[+] IRainman refactoring transfer mechanism
		
		uint64_t m_iHighSpeedStartTick;
		
		UploadList uploads;
		UploadList delayUploads;
		CriticalSection cs;
		
		int lastFreeSlots; /// amount of free slots at the previous minute
		
		typedef unordered_map<UserPtr, uint64_t, User::Hash> SlotMap;
		typedef SlotMap::iterator SlotIter;
		SlotMap reservedSlots;
		SlotMap connectingUsers;
		UploadQueueItem::SlotQueue uploadQueue;
		
		size_t addFailedUpload(const UserConnection& source, const string& file, int64_t pos, int64_t size);
		void notifyQueuedUsers(int64_t tick);//[!]IRainman refactoring transfer mechanism add int64_t tick
		
		friend class Singleton<UploadManager>;
		UploadManager() noexcept;
		~UploadManager();
		
		bool getAutoSlot();
		void removeConnection(UserConnection* aConn);
		void removeUpload(Upload* aUpload, bool delay = false);
		void logUpload(const Upload* u);
		
		void testSlotTimeout(uint64_t aTick = GET_TICK()); // !SMT!-S
		
		// ClientManagerListener
		void on(ClientManagerListener::UserDisconnected, const UserPtr& aUser) noexcept;
		
		// TimerManagerListener
		void on(Second, uint64_t aTick) noexcept;
		void on(Minute, uint64_t aTick) noexcept;
		
		// UserConnectionListener
		void on(BytesSent, UserConnection*, size_t, size_t) noexcept;
		void on(Failed, UserConnection*, const string&) noexcept;
		void on(Get, UserConnection*, const string&, int64_t) noexcept;
		void on(Send, UserConnection*) noexcept;
		void on(TransmitDone, UserConnection*) noexcept;
		
		void on(AdcCommand::GET, UserConnection*, const AdcCommand&) noexcept;
		void on(AdcCommand::GFI, UserConnection*, const AdcCommand&) noexcept;
		
		bool prepareFile(UserConnection& aSource, const string& aType, const string& aFile, int64_t aResume, int64_t& aBytes, bool listRecursive = false);
		bool hasUpload(UserConnection& aSource);
		bool canGrantSlotForIP(const string& userIP, const string& myIP);
		// !SMT!-S
#ifdef IRAINMAN_ENABLE_AUTO_BAN
		struct banmsg_t
		{
			uint32_t tick;
			int slots, share, limit, min_slots, max_slots, min_share, min_limit;
			bool same(const banmsg_t &a) const
			{
				return ((slots ^ a.slots) | (share ^ a.share) | (limit ^ a.limit) |
				        (min_slots ^ a.min_slots) |
				        (max_slots ^ a.max_slots) |
				        (min_share ^ a.min_share) | (min_limit ^ a.min_limit)) == 0;
			}
		};
		typedef unordered_map<string, banmsg_t> BanMap;
		typedef BanMap::iterator BanMapIter;
		typedef BanMap::const_iterator BanMapConstIter;
		bool handleBan(UserConnection& aSource, /*bool forceBan,*/ bool noChecks);
		static bool hasAutoBan(const UserPtr& aUser);
		BanMap lastBans;


#endif // IRAINMAN_ENABLE_AUTO_BAN
};

#endif // !defined(UPLOAD_MANAGER_H)

/**
 * @file
 * $Id: UploadManager.h 568 2011-07-24 18:28:43Z bigmuscle $
 */
